package aso.fighting_groups;

import java.io.IOException;

import sim.engine.Schedule;
import sim.engine.SimState;
import sim.field.grid.DoubleGrid2D;
import sim.field.grid.SparseGrid2D;
import sim.util.Bag;

/**
 * The simulation class of FightingGroups.
 */
public class FightingGroups extends SimState {

    /**
     * UID for serialization.
     */
    private static final long serialVersionUID = -4799995559021666442L;

    /**
     * @param args
     *            command line arguments
     */
    public static void main(final String[] args) {
        doLoop(FightingGroups.class, args);
        System.exit(0);
    }

    /**
     * the number of agents to include in the simulation.
     */
    private int agentCount;
    /**
     * the array of grids of agents.
     */
    private SparseGrid2D[] agentGrid;
    /**
     * the array of agents.
     */
    private Agent[] agents;
    /**
     * the average group size.
     */
    private double averageGroupSize = 0;
    /**
     * the diffusion rate of the smell.
     */
    private double diffusionRate = 0.058;
    /**
     * the evaporation rate of the smell.
     */
    private double evaporationRate = 0.993;
    /**
     * the food consumption rate of the agents.
     */
    private double foodConsumptionRate = 1.0;
    /**
     * the food grid.
     */
    private DoubleGrid2D foodGrid;
    /**
     * the probability of food spawning.
     */
    private double foodSpawnProbability = 0.0001;
    /**
     * the height of the grid.
     */
    private int gridHeight;
    /**
     * the width of the grid.
     */
    private int gridWidth;

    /**
     * the number of groups.
     */
    private int groupCount = 0;

    /**
     * the bag of groups.
     */
    private Bag groups = new Bag();

    /**
     * should you log?
     */
    private boolean log = false;
    /**
     * the log's filename.
     */
    private String logFileName = null;
    /**
     * the maximum amount of food.
     */
    private double maxFood = 100;

    /**
     * the maximum group size.
     */
    private int maxGroupSize = 0;

    /**
     * the maximum smell.
     */
    private double maxSmell = 50000;

    /**
     * the smell grid.
     */
    private DoubleGrid2D smellGrid;
    /**
     * the auxiliary smell grid.
     */
    private DoubleGrid2D smellGrid2;
    /**
     * the starvation importance.
     */
    private double starvationImportance = 0.1;
    /**
     * the number of teams.
     */
    private int teamCount = 2;

    /**
     * @param seed
     *            random seed
     */
    FightingGroups(final long seed) {
        this(seed, 100, 100, 100);
    }

    /**
     * @param seed
     *            random seed
     * @param theGridHeight
     *            the height of the grid
     * @param theGridWidth
     *            the width of the grid
     * @param theAgentCount
     *            the number of agents
     */
    private FightingGroups(final long seed, final int theGridHeight, final int theGridWidth, final int theAgentCount) {
        super(seed);
        this.gridHeight = theGridHeight;
        this.gridWidth = theGridWidth;
        this.agentCount = theAgentCount;
        createGrids();
    }

    /**
     * @param seed
     *            the random seed
     * @param theGridHeight
     *            the height of the grid
     * @param theGridWidth
     *            the width of the grid
     * @param theAgentCount
     *            the number of agents
     * @param theFoodSpawnProbability
     *            the probability that new food will spawn
     * @param theTeamCount
     *            the number of teams
     * @param starvationImportance
     *            the importance of starvation
     * @param theFoodConsumptionRate
     *            the rate of food consumption
     * @param theMaxFood
     *            the maximum food
     * @param doLog
     *            if we have to log
     * @param theLogFileName
     *            the log filename
     */
    FightingGroups(final long seed, final int theGridHeight, final int theGridWidth, final int theAgentCount, final double theFoodSpawnProbability, final int theTeamCount, final double starvationImportance, final double theFoodConsumptionRate, final double theMaxFood, final boolean doLog, final String theLogFileName) {
        super(seed);
        this.gridHeight = theGridHeight;
        this.gridWidth = theGridWidth;
        this.agentCount = theAgentCount;
        this.foodSpawnProbability = theFoodSpawnProbability;
        this.teamCount = theTeamCount;
        this.setStarvationImportance(starvationImportance);
        this.foodConsumptionRate = theFoodConsumptionRate;
        this.setMaxFood(theMaxFood);
        this.log = doLog;
        this.logFileName = theLogFileName;
        createGrids();
    }

    /**
     * @param group
     *            the group to add
     */
    final void addGroup(final Group group) {
        getGroups().add(group);
        updateGroupStats();
    }

    /**
     * create the grids.
     */
    private void createGrids() {
        setAgents(new Agent[agentCount]);
        setSmellGrid(new DoubleGrid2D(gridWidth, gridHeight, 0));
        setSmellGrid2(new DoubleGrid2D(gridWidth, gridHeight, 0));
        setFoodGrid(new DoubleGrid2D(gridWidth, gridHeight, 0));

        setAgentGrid(new SparseGrid2D[teamCount]);

        for (int i = 0; i < teamCount; ++i) {
            getAgentGrid()[i] = new SparseGrid2D(gridWidth, gridHeight);
        }
    }

    /**
     * @return the agentGrid
     */
    public final SparseGrid2D[] getAgentGrid() {
        return agentGrid;
    }

    /**
     * @return the agents
     */
    public final Agent[] getAgents() {
        return agents;
    }

    /**
     * @return the average group size
     */
    public final double getAverageGroupSize() {
        return averageGroupSize;
    }

    /**
     * @return the number of agents
     */
    public final int getBugCount() {
        return agentCount;
    }

    /**
     * @return the diffusion rate
     */
    public final double getDiffusionConstant() {
        return getDiffusionRate();
    }

    /**
     * @return the diffusion rate
     */
    public final double getDiffusionRate() {
        return diffusionRate;
    }

    /**
     * @return the evaporation rate
     */
    public final double getEvaporationConstant() {
        return getEvaporationRate();
    }

    /**
     * @return the evaporation rate
     */
    public final double getEvaporationRate() {
        return evaporationRate;
    }

    /**
     * @return the food consumption rate
     */
    public final double getFoodConsumptionRate() {
        return foodConsumptionRate;
    }

    /**
     * @return the foodGrid
     */
    public final DoubleGrid2D getFoodGrid() {
        return foodGrid;
    }

    /**
     * @return the food spawn probability
     */
    public final double getFoodSpawnProbability() {
        return foodSpawnProbability;
    }

    /**
     * @return the height of the grid
     */
    public final int getGridHeight() {
        return gridHeight;
    }

    /**
     * @return the width of the grid
     */
    public final int getGridWidth() {
        return gridWidth;
    }

    /**
     * @return the number of groups
     */
    public final int getGroupCount() {
        return groupCount;
    }

    /**
     * @return the groups
     */
    public final Bag getGroups() {
        return groups;
    }

    /**
     * @return the maxFood
     */
    public final double getMaxFood() {
        return maxFood;
    }

    /**
     * @return the maxGroupSize
     */
    public final int getMaxGroupSize() {
        return maxGroupSize;
    }

    /**
     * @return the maxSmell
     */
    public final double getMaxSmell() {
        return maxSmell;
    }

    /**
     * @return the smellGrid
     */
    public final DoubleGrid2D getSmellGrid() {
        return smellGrid;
    }

    /**
     * @return the smellGrid2
     */
    public final DoubleGrid2D getSmellGrid2() {
        return smellGrid2;
    }

    /**
     * @return the starvationImportance
     */
    public final double getStarvationImportance() {
        return starvationImportance;
    }

    /**
     * @return the number of teams
     */
    public final int getTeamCount() {
        return teamCount;
    }

    /**
     * @param group
     *            remove this group
     */
    final void removeGroup(final Group group) {
        getGroups().remove(group);
        updateGroupStats();
    }

    /**
     * @param theAgentGrid
     *            the agentGrid to set
     */
    public final void setAgentGrid(final SparseGrid2D[] theAgentGrid) {
        this.agentGrid = theAgentGrid;
    }

    /**
     * @param theAgents
     *            the agents to set
     */
    public final void setAgents(final Agent[] theAgents) {
        this.agents = theAgents;
    }

    /**
     * @param theAgentCount
     *            the number of agents
     */
    public final void setBugCount(final int theAgentCount) {
        if (theAgentCount >= 0) {
            agentCount = theAgentCount;
        }
    }

    /**
     * @param theDiffusionRate
     *            the diffusion rate
     */
    public final void setDiffusionConstant(final double theDiffusionRate) {
        if (theDiffusionRate >= 0 && theDiffusionRate <= 1) {
            setDiffusionRate(theDiffusionRate);
        }
    }

    /**
     * @param theDiffusionRate
     *            the diffusionRate to set
     */
    public final void setDiffusionRate(final double theDiffusionRate) {
        this.diffusionRate = theDiffusionRate;
    }

    /**
     * @param theEvaporationRate
     *            the evaporation rate
     */
    public final void setEvaporationConstant(final double theEvaporationRate) {
        if (theEvaporationRate >= 0 && theEvaporationRate <= 1) {
            setEvaporationRate(theEvaporationRate);
        }
    }

    /**
     * @param theEvaporationRate
     *            the evaporation rate
     */
    public final void setEvaporationRate(final double theEvaporationRate) {
        this.evaporationRate = theEvaporationRate;
    }

    /**
     * @param theFoodConsumptionRate
     *            the food consumption rate
     */
    public final void setFoodConsumptionRate(final double theFoodConsumptionRate) {
        this.foodConsumptionRate = theFoodConsumptionRate;
    }

    /**
     * @param theFoodGrid
     *            the foodGrid to set
     */
    public final void setFoodGrid(final DoubleGrid2D theFoodGrid) {
        this.foodGrid = theFoodGrid;
    }

    /**
     * @param theFoodSpawnProbability
     *            the probability of food spawning
     */
    public final void setFoodSpawnProbability(final double theFoodSpawnProbability) {
        this.foodSpawnProbability = theFoodSpawnProbability;
    }

    /**
     * @param theGridHeight
     *            the height of the grid
     */
    public final void setGridHeight(final int theGridHeight) {
        if (theGridHeight > 0) {
            this.gridHeight = theGridHeight;
        }
    }

    /**
     * @param theGridWidth
     *            the width of the grid
     */
    public final void setGridWidth(final int theGridWidth) {
        if (theGridWidth > 0) {
            gridWidth = theGridWidth;
        }
    }

    /**
     * @param theGroups
     *            the groups to set
     */
    public final void setGroups(final Bag theGroups) {
        this.groups = theGroups;
    }

    /**
     * @param theMaxFood
     *            the maxFood to set
     */
    public final void setMaxFood(final double theMaxFood) {
        this.maxFood = theMaxFood;
    }

    /**
     * @param theMaxSmell
     *            the maxSmell to set
     */
    public final void setMaxSmell(final double theMaxSmell) {
        this.maxSmell = theMaxSmell;
    }

    /**
     * @param theSmellGrid
     *            the smellGrid to set
     */
    public final void setSmellGrid(final DoubleGrid2D theSmellGrid) {
        this.smellGrid = theSmellGrid;
    }

    /**
     * @param theSmellGrid2
     *            the smellGrid2 to set
     */
    public final void setSmellGrid2(final DoubleGrid2D theSmellGrid2) {
        this.smellGrid2 = theSmellGrid2;
    }

    /**
     * @param theStarvationImportance
     *            the starvationImportance to set
     */
    public final void setStarvationImportance(final double theStarvationImportance) {
        this.starvationImportance = theStarvationImportance;
    }

    /**
     * @param theTeamCount
     *            the number of teams
     */
    public final void setTeamCount(final int theTeamCount) {
        this.teamCount = theTeamCount;
    }

    @Override
    public final void start() {
        super.start(); // clear out the schedule

        //initialize
        createGrids();
        
        for (int i = 0; i < agentCount; ++i) {
            // They are fighting with respect to ratio between these values but
            // we don't want the ratio to be too big
            double strength = random.nextDouble() + 1.0;
            // not used in current implementation
            double speed = random.nextDouble();
            //start off with some food, so no immediate starving
            double foodAmount = foodConsumptionRate * 5;
            int team = random.nextInt(teamCount);
            getAgents()[i] = new Agent(strength, speed, foodConsumptionRate, foodAmount, team);
            //place agent at random location
            getAgentGrid()[team].setObjectLocation(getAgents()[i], random.nextInt(gridWidth), random.nextInt(gridHeight));
            schedule.scheduleRepeating(getAgents()[i]);
        }

        schedule.scheduleRepeating(Schedule.EPOCH, 1, new FoodManager());
        schedule.scheduleRepeating(Schedule.EPOCH, 2, new FGDiffuser());

        if (log) {
            try {
                schedule.scheduleRepeating(Schedule.EPOCH, 3, new Logger(logFileName));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * update group stats.
     */
    private void updateGroupStats() {
        double size = 0;
        int maxSize = 0;

        for (int i = 0; i < getGroups().size(); ++i) {
            size += ((Group) getGroups().get(i)).getSize();
            if (((Group) getGroups().get(i)).getSize() > maxSize) {
                maxSize = ((Group) getGroups().get(i)).getSize();
            }
        }

        averageGroupSize = size / getGroups().size();
        maxGroupSize = maxSize;
        groupCount = getGroups().size();
    }

}
